Ext.ux.Portal = Ext.extend(Ext.Panel, { layout : 'column', autoScroll : true, cls : 'x-portal', defaultType : 'portalcolumn', initComponent : function(){ Ext.ux.Portal.superclass.initComponent.call(this); this.addEvents({ validatedrop:true, beforedragover:true, dragover:true, beforedrop:true, drop:true }); }, initEvents : function(){ Ext.ux.Portal.superclass.initEvents.call(this); this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig); }, beforeDestroy : function() { if(this.dd){ this.dd.unreg(); } Ext.ux.Portal.superclass.beforeDestroy.call(this); } }); Ext.reg('portal', Ext.ux.Portal); Ext.ux.Portal.DropZone = function(portal, cfg){ this.portal = portal; Ext.dd.ScrollManager.register(portal.body); Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg); portal.body.ddScrollConfig = this.ddScrollConfig; }; Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, { ddScrollConfig : { vthresh: 50, hthresh: -1, animate: true, increment: 200 }, createEvent : function(dd, e, data, col, c, pos){ return { portal: this.portal, panel: data.panel, columnIndex: col, column: c, position: pos, data: data, source: dd, rawEvent: e, status: this.dropAllowed }; }, notifyOver : function(dd, e, data){ var xy = e.getXY(), portal = this.portal, px = dd.proxy; // case column widths if(!this.grid){ this.grid = this.getGrid(); } // handle case scroll where scrollbars appear during drag var cw = portal.body.dom.clientWidth; if(!this.lastCW){ this.lastCW = cw; }else if(this.lastCW != cw){ this.lastCW = cw; portal.doLayout(); this.grid = this.getGrid(); } // determine column var col = 0, xs = this.grid.columnX, cmatch = false; for(var len = xs.length; col < len; col++){ if(xy[0] < (xs[col].x + xs[col].w)){ cmatch = true; break; } } // no match, fix last index if(!cmatch){ col--; } // find insert position var p, match = false, pos = 0, c = portal.items.itemAt(col), items = c.items.items, overSelf = false; for(var len = items.length; pos < len; pos++){ p = items[pos]; var h = p.el.getHeight(); if(h === 0){ overSelf = true; } else if((p.el.getY()+(h/2)) > xy[1]){ match = true; break; } } pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0); var overEvent = this.createEvent(dd, e, data, col, c, pos); if(portal.fireEvent('validatedrop', overEvent) !== false && portal.fireEvent('beforedragover', overEvent) !== false){ // make sure proxy width is fluid px.getProxy().setWidth('auto'); if(p){ px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null); }else{ px.moveProxy(c.el.dom, null); } this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false}; this.scrollPos = portal.body.getScroll(); portal.fireEvent('dragover', overEvent); return overEvent.status; }else{ return overEvent.status; } }, notifyOut : function(){ delete this.grid; }, notifyDrop : function(dd, e, data){ delete this.grid; if(!this.lastPos){ return; } var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p; var dropEvent = this.createEvent(dd, e, data, col, c, pos !== false ? pos : c.items.getCount()); if(this.portal.fireEvent('validatedrop', dropEvent) !== false && this.portal.fireEvent('beforedrop', dropEvent) !== false){ dd.proxy.getProxy().remove(); dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom); if(pos !== false){ if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){ pos++; } c.insert(pos, dd.panel); }else{ c.add(dd.panel); } c.doLayout(); this.portal.fireEvent('drop', dropEvent); // scroll position is lost on drop, fix it var st = this.scrollPos.top; if(st){ var d = this.portal.body.dom; setTimeout(function(){ d.scrollTop = st; }, 10); } } delete this.lastPos; }, // internal cache of body and column coords getGrid : function(){ var box = this.portal.bwrap.getBox(); box.columnX = []; this.portal.items.each(function(c){ box.columnX.push({x: c.el.getX(), w: c.el.getWidth()}); }); return box; }, // unregister the dropzone from ScrollManager unreg: function() { //Ext.dd.ScrollManager.unregister(this.portal.body); Ext.ux.Portal.DropZone.superclass.unreg.call(this); } });